﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace MathService.Graphs
{
    public class LambdaGraphNode<V, W> : IGraphNode<V, W>, IEquatable<LambdaGraphNode<V, W>>
    {
        private readonly Func<V, IEnumerable<V>> f_GetChields;

        private readonly Func<V, V, W> f_GetWeight;
        private readonly bool f_Buffered;
        private IGraphLink<V, W>[] f_Links;

        public IEnumerable<IGraphLink<V, W>> Links
        {
            get
            {
                return f_Buffered
                    ? (f_Links ?? (f_Links = f_GetChields(Value)
                        .Select(v => new LambdaGraphNode<V, W>(v, f_GetChields, f_GetWeight, f_Buffered))
                        .Select(to => new LambdaGraphLink<V, W>(this, to, f_GetWeight, f_Buffered))
                        .Cast<IGraphLink<V, W>>().ToArray()))
                    : f_GetChields(Value)
                        .Select(v => new LambdaGraphNode<V, W>(v, f_GetChields, f_GetWeight))
                        .Select(to => new LambdaGraphLink<V, W>(this, to, f_GetWeight))
                        .Cast<IGraphLink<V, W>>();
            }
        }

        public V Value { get; private set; }

        public LambdaGraphNode<V, W>[] Childs { get { return this.Cast<LambdaGraphNode<V, W>>().ToArray(); } }

        public LambdaGraphNode(V Value, Func<V, IEnumerable<V>> GetChields, Func<V, V, W> GetWeight, bool Buffered = false)
        {
            f_GetChields = GetChields;
            f_GetWeight = GetWeight;
            f_Buffered = Buffered;
            this.Value = Value;
        }

        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = (f_GetChields != null ? f_GetChields.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (f_GetWeight != null ? f_GetWeight.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ EqualityComparer<V>.Default.GetHashCode(Value);
                return hashCode;
            }
        }

        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

        public static bool operator ==(LambdaGraphNode<V, W> left, LambdaGraphNode<V, W> right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(LambdaGraphNode<V, W> left, LambdaGraphNode<V, W> right)
        {
            return !Equals(left, right);
        }

        public override bool Equals(object obj)
        {
            return !ReferenceEquals(null, obj) &&
                   (ReferenceEquals(this, obj) ||
                   obj.GetType() == GetType() &&
                   Equals((LambdaGraphNode<V, W>)obj));
        }

        public bool Equals(LambdaGraphNode<V, W> other)
        {
            return !ReferenceEquals(null, other) && (ReferenceEquals(this, other) ||
                                                     Equals(f_GetChields, other.f_GetChields) &&
                                                     Equals(f_GetWeight, other.f_GetWeight) &&
                                                     EqualityComparer<V>.Default.Equals(Value, other.Value));
        }

        public IEnumerator<IGraphNode<V, W>> GetEnumerator() { return Links.Select(link => link.Node).GetEnumerator(); }

        public override string ToString()
        {
            return string.Format("λ[{0}]", ReferenceEquals(Value, null) ? "" : Value.ToString());
        }
    }

    public class LambdaGraphNode<V> : IGraphNode<V>
    {
        private readonly Func<V, IEnumerable<V>> f_GetChields;
        private readonly bool f_Buffered;
        private IEnumerable<IGraphNode<V>> f_Childs;

        /// <summary>Связи узла</summary>
        public IEnumerable<IGraphNode<V>> Childs
        {
            get
            {
                return f_Buffered
                    ? f_Childs ?? (f_Childs = f_GetChields(Value)
                        .Select(value => new LambdaGraphNode<V>(value, f_GetChields, f_Buffered))
                        .Cast<IGraphNode<V>>()
                        .ToArray())
                    : f_GetChields(Value)
                        .Select(value => new LambdaGraphNode<V>(value, f_GetChields, f_Buffered))
                        .Cast<IGraphNode<V>>();
            }
        }

        /// <summary>Значение узла</summary>
        public V Value { get; set; }

        public LambdaGraphNode<V>[] ChildsArray
        {
            get { return Childs.Cast<LambdaGraphNode<V>>().ToArray(); }
        }

        public LambdaGraphNode(V Value, Func<V, IEnumerable<V>> GetChields, bool Buffered = false)
        {
            f_GetChields = GetChields;
            f_Buffered = Buffered;
            this.Value = Value;
        }

        IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }

        public IEnumerator<IGraphNode<V>> GetEnumerator() { return Childs.GetEnumerator(); }
    }
}